/**
 * PageView plugin for jQuery
 * @author Kain Haart <dev@mail.kain-haart.info>
 * @version 0.4
 * @requires jQuery (created with 1.3.2) 
 */

//(function($){

// HASH and PARAMETERS routine

/**
 * Convert URL parameter string into associative array $key=>$value
 * @param $sParameters String URL parameters string
 * @return Object (Assoc. array) Extracted parameters
 */
var funcDecodeParameters = function($sParameters)
	{
	if($sParameters.charAt(0)=="?")
		$sParameters = $sParameters.slice(1);
	
	var $aParameters = {};
	var $aParamChunks = $sParameters.split("&");
	$.each($aParamChunks, function()
		{
		var $aP = this.split("=");
		$aParameters[$aP[0]]=$aP[1];
		});
	
	return $aParameters;
	};

/**
 * Convert associative array into URL parameters string
 * @param $aParameters Object (Assoc. array) Parameters 
 * @return string URL parameters string
 */	
var funcEncodeParameters = function($aParameters)
	{
	var $sParameters = "";
	$.each($aParameters, function($sIndex,$sValue)
		{
		if($sParameters!="")
			$sParameters += "&";
		$sParameters += ($sIndex+"="+$sValue);
		});
	return $sParameters;
	};
	
/**
 * Read window.location.hash into internal-format hash object 
 * @return Object (Structure) $Hash {base: String (before parameters), params: Assoc.Array}
 */
var funcReadHash = function()
	{
	var $aHash = {};
	// Getting string
	var $sHash = window.location.hash;
	if($sHash.charAt(0)=="#")
		$sHash = $sHash.slice(1);
	// Splitting string
	var $aHashChunks = $sHash.split("?");
	// Base
	$aHash.base = $aHashChunks[0];
	// Params
	if(!$aHashChunks[1])
		{
		$aHash.params = {};
		}
	else
		{
		// TODO: Support multiple "?" in hash - implode last chunks
		var $sHashParams = $aHashChunks[1] ? $aHashChunks[1] : "";
		$aHash.params = funcDecodeParameters($sHashParams);
		}
	return $aHash;
	};

// TODO: MAYBE it (and other "procedures") should return at least boolean?
/**
 * Writes window.location.hash from internal-format hash object
 * @param $aHash Object (Structure) internal-format hash object
 * @return
 */
var funcWriteHash = function($aHash)
	{
	var $sParams = $aHash.params ? ("?"+funcEncodeParameters($aHash.params)) : "";
	window.location.hash = $aHash.base + $sParams;
	};
	
// GENERAL METHODS
	
/**
 * Internal function. Display PageView's specified page
 * @param $PageView jQuery PageView
 * @param $iPage Integer Page
 * @return Integer Displayed page number
 */
var funcShowPage = function($PageView,$iPage)
	{
	// Env
	var options = getOptions($PageView);
	var $nPages = getPages($PageView);
	var $nPageSize = options.nPageSize;
	
	// Correcting page number
	if($iPage < 1)
		$iPage = 1;
	if($iPage > $nPages)
		$iPage = $nPages;
	var $iPageStart = ($iPage-1)*$nPageSize;
	// Show/Hide Items
	$(options.PageItem,$PageView)
		.filter("."+options.classes.ActivePageItem)
			.removeClass(options.classes.ActivePageItem)
			.hide()
			.end()
		.slice($iPageStart,$iPageStart+$nPageSize)
			.addClass(options.classes.ActivePageItem)
			.show()
			;
	setCurrentPage($PageView,$iPage);
	
	// Displaying state : factual active page
	// Changing abacus state
	var $Abacus = getAbacus($PageView);
	if($Abacus)
		funcAbacusShowState($Abacus,$iPage,options);
	
	return $iPage;
	};

/**
 * External method. Switch PageView to show a specified page
 * @param $iPage Integer
 * @return jQuery
 */
var pvShowPage = function($iPage)
	{
	return this.each(function()
		{
		// Env
		var $PageView = $(this);
		
		$iPage = funcShowPage($PageView,$iPage);
		$PageView.pvWriteState({"page":$iPage});
		});
	};

/**
 * External method. Switch PageView to Show specified item 
 * @param $sItemId String Item's DOM id
 * @return jQuery
 */
var pvShowItem = function($sItemId)
	{
	return this.each(function()
		{
		// Env
		var $PageView = $(this);
		var options = getOptions($PageView);
		var $nPageSize = options.nPageSize;
		
		// Selecting item
		var $Item = $($sItemId,$PageView).eq(0);
		if(!$Item.size())
			{
			// Item not found
			// var $iPage=1;
			}
		else
			{
			var $iItemIndex = 1 + $(options.PageItem,$PageView).index($Item);
			var $iPage = Math.ceil($iItemIndex/$nPageSize);
			funcShowPage($PageView,$iPage);
			}
		$PageView.pvWriteState({"item":$sItemId});
		});
	};
	
// ABACUS

/**
 * Internal function. Actualy creates PageView's Abacus in DOM and links it
 * @param $PageView jQuery PageView
 * @param $Abacus jQuery Abacus container(s)
 * @return
 */	
var funcCreateAbacus = function($PageView,$Abacus)
	{
	// Configuration
	var options = getOptions($PageView);
	var $nPages = getPages($PageView);
	var $sHref = options.sHrefBase+"?"+options.parameters.page+"=";

	// Creating HTML
	var $sHTML = "";
	for(var $i = 1; $i <= $nPages; $i++)
		$sHTML = $sHTML 
			+ "<a"
			+ " href='"+($sHref)+($i)+"'"
			+ " class='"+(options.classes.AbacusLinkPrefix)+($i)+"'"
			+ ">"
			+ ($i)
			+ "</a>";
	
	// Constructing Abacus
	$Abacus
		.html($sHTML)
		.children("a")
			.click(function()
				{
				var $Link = $(this);
				var $Abacus = $Link.parent();
				var $PageView = getPageView($Abacus);
				var $iPage = parseInt($Link.attr("href").slice($sHref.length));
				$PageView.ShowPage($iPage);
				return false;
				}
		);
	
	// Linking
	linkPageViewAbacus($PageView,$Abacus);
	
	// Setting active page
	var $iPage;
	if($iPage = getCurrentPage($PageView))
		funcAbacusShowState($Abacus,$iPage,options);
	};
	
// TODO: Maybe: Enclose abacus options into separate options brunch and allow to change it for each abacus instance separately?

/**
 * Changes abacus state in DOM according to PageView's State and options
 * @param $Abacus jQuery Abacus
 * @param $iPage Integer PageView active page
 * @param options Object (Structure) PageView's options
 * @return
 */
var funcAbacusShowState = function($Abacus,$iPage,options)
	{
	$("a."+options.classes.AbacusActiveLink,$Abacus)
		.removeClass(options.classes.AbacusActiveLink);
	$("a."+options.classes.AbacusLinkPrefix+$iPage,$Abacus)
		.addClass(options.classes.AbacusActiveLink);
	}; 
	
/**
 * External method. Create abacus in PageView chain
 * @this jQuery PageView
 * @param $Abacus Selector Elements to create abacus(es) in
 * @return jQuery
 */
var pvCreateAbacusIn = function($Abacus)
	{
	var $Abacus = $($Abacus);
	return this.each(function()
		{
		var $PageView = $(this);
		funcCreateAbacus($PageView,$Abacus);
		});
	};

// TODO: Handle multi-element jQuery as PageView. Can Abacus switch multiple pageview or filter with .eq(0)?

/**
 * External method. Create abacus in Abacus chain
 * @this jQuery Abacus
 * @param $PageView Selector PageView for which abacus is creating
 * @return
 */
var pvCreateAbacusOf = function($PageView)
	{
	var $PageView = $($PageView);
	return this.each(function()
		{
		var $Abacus = $(this);
		funcCreateAbacus($PageView,$Abacus);
		});
	};
	
// STATE

/**
 * Rename state parameters into theirs URL nicknames
 * @param $aState Object (Assoc. array) State parameters
 * @param $aParamsCode Object (Assoc. array) Encoding options [$name=>$nickname]
 * @return Object (Assoc. array) 
 */
var funcEncodeState = function($aState,$aParamsCode)
	{
	var $aEncodedState = {};
	$.each($aState,function($sKey,$sValue)
		{
		if($aParamsCode[$sKey])
			$aEncodedState[$aParamsCode[$sKey]] = $sValue;
		});
	return $aEncodedState;
	};
	
/**
 * Rename state parameters from theirs URL nicknames
 * @param $aState Object (Assoc. array) State parameters (encoded)
 * @param $aParamsCode Object (Assoc. array) Encoding options [$name=>$nickname]
 * @return Object (Assoc. array)
 */
var funcDecodeState = function($aState,$aParamsCode)
	{
	// Reversing coding array
	var $aReversedCode = {};
	$.each($aParamsCode, function($sKey,$sValue)
		{
		$aReversedCode[$sValue] = $sKey;
		});
	// Translating with reversed  coding array
	var $aDecodedState = {};
	$.each($aState,function($sKey,$sValue)
		{
		if($aReversedCode[$sKey])
			$aDecodedState[$aReversedCode[$sKey]] = $sValue;
		});
	return $aDecodedState;
	};
	
/**
 * External method. Switches PageView using State object
 * @param $aState Object (Structure) State. Can use 2 keys: "item" and "page"
 * @return jQuery
 */
var pvChangeState = function($aState)
	{
	var $PageView = this;

	// Searching method matching to state
	if($aState.item)
		$PageView.ShowItem($aState.item);
	else if ($aState.page)
		$PageView.ShowPage(parseInt($aState.page));
	
	return this;
	};

/**
 * External method. Switches PageView using State reading from window.location.hash
 * @return jQuery
 */
var pvReadState = function()
	{
	if(!window.location.hash) return false;
	var $aParams = funcReadHash().params;
	//if($aParams.isEmpty) return false;
	
	return this.each(function()
		{
		var $PageView = $(this);
		var $aParamsCode = getOptions($PageView).parameters;
		var $aState = funcDecodeState($aParams,$aParamsCode);
		//if(!$aState.isEmpty)
		$PageView.pvChangeState($aState);
		});
	};
	
/**
 * Writing PageView state into window.location.hash
 * @this jQuery PageView
 * @param $aState Object (Structure) [Optional] State to set instead of getting (@see pvChangeState) 
 * @return jQuery
 */	
var pvWriteState = function($aState)
	{					
	return this.each(function()
		{
		// Env
		var $PageView = $(this);
		var options = getOptions($PageView);
		
		// Option not to change hash
		if(options.ChangeHash == "none")
			return true;
		
		// Configuring base
		var $aHash;
		if(options.ChangeHash == "new")
			{
			$aHash = {
				'base':	options.sHashBase,
				'params' : {}
				};
			}
		else if(options.ChangeHash == "modify")
			{
			// Reading current hash
			$aHash = funcReadHash();
			// Cleaning state params
			// TODO: Clean in circle of options.parameters 
			delete $aHash.params[options.parameters.page];
			delete $aHash.params[options.parameters.item];
			}
		
		// Configuring state
		if($aState)
			setState($PageView,$aState);
		else
			$aState = getState($PageView);

		if($aState)
			$.extend($aHash.params,funcEncodeState($aState,options.parameters));
		
		// Writing hash
		funcWriteHash($aHash);
		});
	};

// CONSTRUCTOR

/**
 * Pageview constructor. Transforms elements to page view
 * @this jQuery Elements to transform using PageView 
 * @param $UserOptions Object (Structure) PageView Options
 * @return jQuery
 */	
var pvPageView = function($UserOptions)
	{					
	return this.each(function()
		{
		// Creating jquery object
		var $PageView = $(this);
		
		// Merging options
		var options = $.extend(true, {},
			$.pvConfig.Defaults,
			getOptions($PageView),
			$UserOptions
			);
		
		// Calculate Parameters
		var $nPageSize = options.nPageSize;
		var $nItems = $(options.PageItem,$PageView).size();
		var $nPages = Math.ceil($nItems/$nPageSize);
		if($nPages==0)
			$nPages=1;
		
		// Writing data
		setOptions($PageView,options);
		setPages($PageView,$nPages);
		//setItems($nItems);
		
		// Creating or updating Abacus
		var $Abacus = $([]).add(getAbacus($PageView));
		if(options.mAbacusContainer)
			{
			var $AbacusContainer = options.bAbacusInside ? $(options.mAbacusContainer,$PageView) : $(options.mAbacusContainer);
			$Abacus = $Abacus.add($AbacusContainer);
			}
		if($Abacus.size())
			funcCreateAbacus($PageView,$Abacus);
		
		// Hiding Items
		$(options.PageItem,$PageView).hide();
		
		// Marker object as paged
		$PageView.addClass($.pvConfig.classes.isPaged);
		
		// Changing state to initial
		if(options.bInitReadState)
			$PageView.pvReadState();
		if(!getCurrentPage($PageView))
			$PageView.pvChangeState(options.aInitState);
		});
	};

/**
 * Set PageView default options
 * @param $Options Object (Structure)
 * @return
 */	
var pvDefaults = function($Options)
	{
	$.extend(true,$.pvConfig.Defaults,$Options);
	};
	
// ATTRIBUTES DATA Setters/Getters
	
// pvOptions : structure
var getOptions = function($PageView){return $PageView.data("pvOptions");};
var setOptions = function($PageView,$Data){$PageView.data("pvOptions",$Data);};
// pvPages : integer
var getPages = function($PageView){return $PageView.data("pvPages");};
var setPages = function($PageView,$Data){$PageView.data("pvPages",$Data);};
// pvCurrentPage : integer
var getCurrentPage = function($PageView){return $PageView.data("pvCurrentPage");};
var setCurrentPage = function($PageView,$Data){$PageView.data("pvCurrentPage",$Data);};
// pvState : structure
var getState = function($PageView){return $PageView.data("pvState");};
var setState = function($PageView,$Data){$PageView.data("pvState",$Data);};

// LINK DATA Setters/Getters
var linkPageViewAbacus = function($PageView,$Abacus)
	{
	$Abacus.data("pvPageView",$PageView);
	$PageView.data("pvAbacus",$Abacus.add(getAbacus($PageView)));
	};
var getAbacus = function($PageView){return $PageView.data("pvAbacus");};
var getPageView = function($Abacus){return $Abacus.data("pvPageView");};

//INTERFACE

/**
 * Map for extending jQuery methods with PageView external methods [$method_name => method] 
 */	
var $pvInterface = {
	// Constructor
	'PageView' : pvPageView,
	// Configurator
	'pvDefaults' : pvDefaults, // TODO: Explore way of assigning $. module
	// General Methods
	'ShowPage' : pvShowPage,
	'ShowItem' : pvShowItem,
	// State methods
	'pvChangeState' : pvChangeState,
	'pvReadState' : pvReadState,
	'pvWriteState' : pvWriteState, // TODO: Remove from interface
	// Abacus
	'CreateAbacusIn' : pvCreateAbacusIn,
	'CreateAbacusOf' : pvCreateAbacusOf
};
$.fn.extend($pvInterface);
	
// CONFIG
	
var $pvConfig = {};
// Classes
$pvConfig.classes = {};
//TODO: Is this marker realy needed?
$pvConfig.classes.isPaged = "isPageView";
// Defaults
$pvConfig.Defaults =
	{
	// Page size
	nPageSize : 10,
	// Selectors
	PageItem: ".PageItem",
	// Classes
	classes : 
		{
		ActivePageItem : "ActivePageItem",
		AbacusLinkPrefix : "Abacus",
		AbacusActiveLink : "AbacusCurrentPage"
		},
	// Change state in location hash
	ChangeHash : "new",
	sHashBase : "",
	sHrefBase : "",
	parameters :
		{
		page : "page",
		item : "item"
		},
	// Abacus auto-construct
	mAbacusContainer : ".PageAbacus",
	bAbacusInside : true,
	// Initial state
	bInitReadState : true,
	aInitState : {"page":1}
	};
// Assigning into $
$.pvConfig = $pvConfig;

//})(jQuery);